//
//  Message.m
//  HigherOrderMessaging
//
//  Created by Ofri Wolfus on 26/08/06.
//  Copyright 2006 Ofri Wolfus. All rights reserved.
//

#import "Message.h"
#include <objc/objc-runtime.h>
#include <malloc/malloc.h>
#import "HOMUtilities.h"
#include "DPObjC-Compatibility.h"
#import <Foundation/NSInvocation.h>
#import "HOMInvocationBuilder.h"


/*
 * This file is the base of @HOM.
 * It contains the machanism that turns a Objective-C message into a Message instance,
 * and the entire framework relies on this.
 * NOTE: This is not possible without the ID() macro that tricks GCC to think the MSG() macro
 * returns an object.
 */


id _sharedMessageBuilder = nil;		// No longer used

@interface Message (MessageBuilderSupport)
- (void)dealloc;
@end

#define _returnesStruct(list) (marg_getValue(list, 0, void *) != _sharedMessageBuilder)

@implementation Message

static Class autoreleasePoolCls = Nil;

+ (void)initialize {
	autoreleasePoolCls = objc_getClass("NSAutoreleasePool");
}

+ (id)_messageWithArgs:(marg_list)list method:(MethodDescription)desc {
	Message *m = class_createInstance(self, 0);
	
	if (m) {
		size_t len = strlen(description_getTypes(desc));
		char *types = calloc(len+1, sizeof(char));
		
		memcpy(types, description_getTypes(desc), len * sizeof(char));
		
		m->refCount = 1U;
		m->args = list;
		//marg_setValue(m->args, (_returnesStruct(list) ? sizeof(void *) : 0), id, nil);
		m->argsSize = description_getSizeOfArguments(desc);	// We cache the size of the method in order to speed up some HOM implementations
		m->description = description_createDescription(description_getName(desc), types);
	}
	
	return [m autorelease];
}

- (void)dealloc {
	free(args);
	free((void *)description_getTypes(description));	// We copied the types at initialization time
	free(description);
	free(self);
}

- (SEL)selector {
	return marg_getValue(args, (_returnesStruct(args) ? sizeof(void *) : 0) + sizeof(id), SEL);
}

- (marg_list)arguments {
	return args;
}

- (unsigned)argumentsSize {
	return argsSize;
}

- (BOOL)returnsStruct {
	return _returnesStruct(args);
}

- (unsigned)numberOfArguments {
	return hom_getNumberOfArguments([self selector]);
}

- (const char *)types {
	return description_getTypes(description);
}

- (MethodDescription)methodDescription {
	return description;
}

- (id)forward:(SEL)sel :(marg_list)arglist {
	return nil;
}

- (id)retain {
	++refCount;
	return self;
}

- (id)autorelease {
	if (autoreleasePoolCls)
		[autoreleasePoolCls addObject:self];
	
	return self;
}

- (void)release {
	--refCount;
	
	if (refCount == 0U)
		[self dealloc];
}

- (unsigned)retainCount {
	return refCount;
}

- (NSString *)description {
	return [NSString stringWithFormat:@"<0x%x> Message: \"%s\"", self, sel_getName([self selector])];
}

- (id)sendTo:(id)receiver {
	return objc_msgSendv(receiver, [self selector], argsSize, args);
}

@end

@implementation Message (CocoaConventions)

- (id)invocationWithTarget:(id)target {
	NSInvocation *r;
	id builder = [HOMInvocationBuilder builderForTarget:target];
	
	r = [self sendTo:builder];
	[builder dealloc];
	return r;
}

@end

//=================================================================================//
//=================================================================================//
//=================================================================================//

// Holds all our cached method descriptions
static CFMutableDictionaryRef _methodsCache;

// Finds a method description for the given selector, which has the largest arguments size.
// The returned description must be freed properly.
MethodDescription _hom_findMethodForSelector(SEL sel) {
	static int			classCount = 0;		// The number of classes that was in our previous search
	static Class		*classes = NULL;	// A block with pointers to all classes
	int					count;
	MethodDescription	result;
	Method				meth = NULL;
	NSString			*key = NSStringFromSelector(sel);
	
	// Like in the ObjC runtime, thread synchronization is always active
	@synchronized ((NSDictionary *)_methodsCache) {
		// Set up our cache dictionary if it wasn't already active
		if (!_methodsCache)
			_methodsCache = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL);
		
		// Look for a cached version of our method.
		// If no new classes were registered since the last time we did a search, this method will be used
		result = (MethodDescription)CFDictionaryGetValue(_methodsCache, key);
		
		// Get the current number of classes registered with the runtime
		count = objc_getClassList(NULL, 0);
		
		/*
		 * We work only if new classes were registered since our last search.
		 * NOTE: When unloading of classes is implemened this check may not be enough
		 * if some classes were added, and some others were removed but the total number of classes remained the same.
		 * Even in this situation though, the chances are very small the new classes added new method for existing selector(s)
		 * with a different size of total arguments.
		 */
		if (count != classCount) {
			// Allocate enough memory for pointers to all classes available
			if (!classes)
				classes = malloc(sizeof(Class) * count);
			else
				classes = reallocf(classes, sizeof(Class) * count);
			
			if (classes) {
				// Remember the current number of classes
				classCount = count;
				
				// Get all available classes from the runtime
				objc_getClassList(classes, classCount);
				
				// Clean up our cache
				CFDictionaryRemoveAllValues(_methodsCache);
			}
			
			// Free our previous cached method
			if (result) {
				//free(result->method_types);
				free(result);
				result = NULL;
			}
		}
		
		if (!result) {
			Class		cls;
			int			i;
			Method		m = NULL;
			unsigned	argsSize = 0U;
			
			/*
			 * Loop through all classes and search for a method with a matching selector
			 * This is a very long loop and that's why we use our cache if possible
			 * NOTE: Since method lookups scans through all super classes, we can cut down the number of lookups
			 * by remembering which super classes were already searched and skip those.
			 */
			for (i = 0; i < classCount; i++) {
				cls = classes[i];
				
				// First, attempt to get an instance method
				m = class_getInstanceMethod(cls, sel);
				
				// If it's not available, try a class method
				if (!m)
					m = class_getClassMethod(cls, sel);
				
				/*
				 * If we found a method, we need to get the size of its arguments.
				 * Since we can't know which method will be used in practice, we'll pick the one with the largest
				 * size so that we don't accidentally cut some arguments.
				 */
				if (m) {
					unsigned s = method_getSizeOfArguments(m);
					
					if (s > argsSize) {
						meth = m;
						argsSize = s;
					}
				}
			}
			
			if (meth) {
				//unsigned len = strlen(meth->method_types);
				
				// Allocate a new method, and copy the method we just found.
				// NOTE: The IMP of our method always points to NULL.
				/*result = calloc(1, sizeof(struct objc_method));
				result->method_name = sel;
				result->method_types = malloc(sizeof(char) * (len + 1));
				strcpy(result->method_types, meth->method_types);*/
				result = dp_copyMethodDescription(meth);
				
				// Cache our method description
				CFDictionarySetValue(_methodsCache, key, result);
			}
		}
	}
	
	return result;
}

//=================================================================================//
//=================================================================================//
//=================================================================================//

/*
 * The MessageBuilder class is a special class that responds to has no methods.
 * Every message sent to it will be packed and returned as a Message instance.
 * Unlike normal singleton objects, there is no instance of this class and messages will be sent
 * to the class directly.
 */
@interface MessageBuilder {
	id isa;
}
@end

@implementation MessageBuilder

// Set up the public pointer to ourself
+ (void)load {
	_sharedMessageBuilder = self;
}

// The runtime automatically calls this method, so we make sure it won't get accidentally forwarded.
+ (void)initialize {
}

// Catche all messages and turn them into Message instances
- (id)forward:(SEL)sel :(marg_list)args {
	marg_list			list;
	MethodDescription	d = _hom_findMethodForSelector(sel);
	
	if (d) {
		//marg_malloc(list, m);
		list = dp_marg_malloc(description_getSizeOfArguments(d));
		memcpy(list, args, malloc_size(list));
		
		return [Message _messageWithArgs:list method:d];
	}
	
	return nil;
}

@end

